# SEE modeldata package for new datasets
library(xlsx)
library(tidyverse)         # for graphing and data cleaning
library(tidymodels)        # for modeling
library(stacks)            # for stacking models
library(naniar)            # for examining missing values (NAs)
library(lubridate)         # for date manipulation
library(moderndive)        # for King County housing data
library(vip)               # for variable importance plots
library(DALEX)             # for model interpretation  
library(DALEXtra)          # for extension of DALEX
library(patchwork)         # for combining plots nicely
theme_set(theme_minimal()) # Lisa's favorite theme
options(scipen=999)
data("lending_club")
# Data dictionary (as close as I could find): https://www.kaggle.com/wordsforthewise/lending-club/discussion/170691

When you finish the assignment, remove the # from the options chunk at the top, so that messages and warnings aren’t printed. If you are getting errors in your code, add error = TRUE so that the file knits. I would recommend not removing the # until you are completely finished.

Put it on GitHub!

From now on, GitHub should be part of your routine when doing assignments. I recommend making it part of your process anytime you are working in R, but I’ll make you show it’s part of your process for assignments.

Task: When you are finished with the assignment, post a link below to the GitHub repo for the assignment. If you want to post it to your personal website, that’s ok (not required). Make sure the link goes to a spot in the repo where I can easily find this assignment. For example, if you have a website with a blog and post the assignment as a blog post, link to the post’s folder in the repo. As an example, I’ve linked to my GitHub stacking material here.

https://github.com/thytng/stat494-assignment2

Modeling

Before jumping into these problems, you should read through (and follow along with!) the model stacking and global model interpretation tutorials on the Course Materials tab of the course website.

We’ll be using the lending_club dataset from the modeldata library, which is part of tidymodels. The data dictionary they reference doesn’t seem to exist anymore, but it seems the one on this kaggle discussion is pretty close. It might also help to read a bit about Lending Club before starting in on the exercises.

The outcome we are interested in predicting is Class. And according to the dataset’s help page, its values are “either ‘good’ (meaning that the loan was fully paid back or currently on-time) or ‘bad’ (charged off, defaulted, of 21-120 days late)”.

Tasks: I will be expanding these, but this gives a good outline.

  1. Explore the data, concentrating on examining distributions of variables and examining missing values.

Loading the data dictionary downloaded from Kaggle:

(dict <- read.xlsx("LCDataDictionary.xlsx", sheetIndex = 1)[,1:2] %>%
   filter(LoanStatNew %in% names(lending_club))) 

Exploring numerical variables:

lending_club %>%
  select(where(is.numeric)) %>%
  pivot_longer(cols = everything(),
               names_to = "variable",
               values_to = "values") %>%
  ggplot(aes(x = values)) +
  geom_histogram() +
  facet_wrap(vars(variable),
             scales = "free")

Categorical variables:

lending_club %>%
  select(is.factor) %>%
  pivot_longer(cols = everything(),
               names_to = "variable",
               values_to = "value") %>%
  ggplot(aes(x = value)) +
  geom_bar() +
  facet_wrap(~variable, 
             scale = "free") +
  theme(axis.text.x = element_text(angle = 30, hjust = 1, size = 6))

Checking for missing data:

lending_club %>%
  add_n_miss() %>%
  count(n_miss_all)
  1. Do any data cleaning steps that need to happen before the model is build. For example, you might remove any variables that mean the same thing as the response variable (not sure if that happens here), get rid of rows where all variables have missing values, etc.

There’s no missing / null data and I couldn’t find any redundant variables. emp_length is a bit tricky because it should be the employment length in years but the variable is categorical in the data. I can make it continuous but there are categories for less than 1 year, more than 9 years, and unknown so it’s probably best to leave it untouched.

Be sure to add more “bad” Classes. This is not the best solution, but it will work for now. (Should investigate how to appropriately use step_sample_up() function from themis).

create_more_bad <- lending_club %>% 
  filter(Class == "bad") %>% 
  sample_n(size = 3000, replace = TRUE)

lending_club_mod <- lending_club %>% 
  bind_rows(create_more_bad)
  1. Split the data into training and test, putting 75% in the training data.
set.seed(494) # for reproducibility
lc_split <- initial_split(lending_club_mod, prop = .75)
lc_training <- training(lc_split)
lc_testing <- testing(lc_split)
  1. Set up the recipe and the pre-processing steps to build a lasso model. Some steps you should take:
  • Make all integer variables numeric (I’d highly recommend using step_mutate_at() or this will be a lot of code). We’ll want to do this for the model interpretation we’ll do later.
  • Think about grouping factor variables with many levels.
  • Make categorical variables dummy variables (make sure NOT to do this to the outcome variable).
  • Normalize quantitative variables.
lc_lasso_recipe <- recipe(Class ~ ., data = lending_club_mod) %>%
  # convert all integers to numerics
  step_mutate_at(is.integer, fn = as.numeric) %>%
  step_mutate(
    # merge all subgrades with the same letter grade into one category
    grade = substr(sub_grade, 0, 1),
    # convert address state to address region
    addr_region = fct_relevel(
      case_when(
        addr_state %in% c("CT", "MA", "ME", "NH", "RI", "VT") ~ "Northeast",
        addr_state %in% c("DE", "MD", "NJ", "NY", "PA", "DC") ~ "Mid-Atlantic",
        addr_state %in% c("AL", "AR", "FL", "GA", "KY", "LA", 
                          "MS", "NC", "SC", "TN", "VA", "WV") ~ "South",
        addr_state %in% c("IA", "IL", "IN", "KS", "MI", "MN", 
                          "MO", "ND", "NE", "OH", "SD", "WI") ~ "Midwest",
        addr_state %in% c("AK", "CA", "CO", "HI", "ID", "MT", 
                          "NV", "OR", "UT", "WA", "WY") ~ "West",
        addr_state %in% c("AZ", "NM", "OK", "TX") ~ "Southwest"
        ))) %>%
  # add a constant to the variables we will log-transform because there are 0s in the data
  step_mutate_at(annual_inc, delinq_amnt, total_bal_il, total_il_high_credit_limit, 
                 fn = function(x) x+1) %>%
  # log transform with base 10
  step_log(annual_inc, delinq_amnt, total_bal_il, total_il_high_credit_limit, base = 10) %>%
  # remove the old categorical variables that we refactored
  step_rm(sub_grade, addr_state) %>%
  step_mutate_at(grade, fn = as.factor) %>%
  # create dummy variables for categorical vars
  step_dummy(all_nominal(), -all_outcomes()) %>%
  # normalize quantitative vars
  step_normalize(all_predictors(), -all_nominal()) 
lc_lasso_recipe %>%
  prep(lc_training) %>%
  juice() 
  1. Set up the lasso model and workflow. We will tune the penalty parameter.
lc_lasso_mod <- 
  logistic_reg(mixture = 1) %>%
  set_engine("glmnet") %>%
  set_args(penalty = tune()) %>%
  set_mode("classification")
lc_lasso_wf <- workflow() %>%
  add_recipe(lc_lasso_recipe) %>%
  add_model(lc_lasso_mod)
  1. Set up the model tuning for the penalty parameter. Be sure to add the control_stack_grid() for the control argument so we can use these results later when we stack. Find the accuracy and area under the roc curve for the model with the best tuning parameter. Use 5-fold cv.
set.seed(494) 
penalty_grid <- grid_regular(penalty(), levels = 4)
ctrl_grid <- control_stack_grid()
metric <- metric_set(roc_auc)
lc_cv <- vfold_cv(lc_training, v = 5)

lc_lasso_tune <- lc_lasso_wf %>%
  tune_grid(resamples = lc_cv,
            grid = penalty_grid,
            control = ctrl_grid, 
            metrics = metric)
  1. Set up the recipe and the pre-processing steps to build a random forest model. You shouldn’t have to do as many steps. The only step you should need to do is making all integers numeric.
lc_rf_recipe <- recipe(Class ~ ., data = lending_club_mod) %>%
  step_mutate_at(is.integer, fn = as.numeric) 
  1. Set up the random forest model and workflow. We will tune the mtry and min_n parameters and set the number of trees, trees, to 100 (otherwise the next steps take too long).
lc_rf_mod <- rand_forest(mtry = tune(), 
                         min_n = tune(), 
                         trees = 100) %>% 
  set_mode("classification") %>% 
  set_engine("ranger")
lc_rf_wf <- workflow() %>%
  add_recipe(lc_rf_recipe) %>%
  add_model(lc_rf_mod)
  1. Set up the model tuning for both the mtry and min_n parameters. Be sure to add the control_stack_grid() for the control argument so we can use these results later when we stack. Use only 3 levels in the grid. For the mtry parameter, you need to put finalize(mtry(), lending_training %>% select(-Class)) in as an argument instead of just mtry(), where lending_training is the name of your training data. This is because the mtry() grid will otherwise have unknowns in it. This part can take a while to run.
rf_grid <- grid_regular(finalize(mtry(), lc_training %>% select(-Class)),
                        min_n(),
                        levels = 3)

lc_rf_tune <- lc_rf_wf %>%
  tune_grid(resamples = lc_cv,
            control = ctrl_grid,
            grid = rf_grid,
            metrics = metric)
  1. Find the best tuning parameters. What is the are the accuracy and area under the ROC curve for the model with those tuning parameters?
best_lasso_param <- lc_lasso_tune %>%
  select_best()

lc_lasso_tune %>%
  collect_metrics() %>%
  filter(penalty == best_lasso_param$penalty)
best_rf_param <- lc_rf_tune %>%
  select_best()

lc_rf_tune %>%
  collect_metrics() %>%
  filter(mtry == best_rf_param$mtry & min_n == best_rf_param$min_n)
  1. Use functions from the DALEX and DALEXtra libraries to create a histogram and boxplot of the residuals from the training data. How do they look? Any interesting behavior?
# fit final models
lc_lasso_final_mod <- lc_lasso_wf %>%
  finalize_workflow(best_lasso_param) %>%
  fit(data = lc_training)

lc_rf_final_mod <- lc_rf_wf %>%
  finalize_workflow(best_rf_param) %>%
  fit(data = lc_training)
# create explainers 
lasso_explain <- 
  explain_tidymodels(
    model = lc_lasso_final_mod,
    data = lc_training %>% select(-Class), 
    y = lc_training %>% mutate(Class_num = as.integer(Class =="good")) %>% pull(Class_num),
    label = "lasso",
    type = "classification"
  )
## Preparation of a new explainer is initiated
##   -> model label       :  lasso 
##   -> data              :  9643  rows  22  cols 
##   -> data              :  tibble converted into a data.frame 
##   -> target variable   :  9643  values 
##   -> predict function  :  yhat.workflow  will be used (  default  )
##   -> predicted values  :  No value for predict function target column. (  default  )
##   -> model_info        :  package tidymodels , ver. 0.1.2 , task classification (  default  ) 
##   -> model_info        :  type set to  classification 
##   -> predicted values  :  numerical, min =  0.02352637 , mean =  0.7258115 , max =  0.9999158  
##   -> residual function :  difference between y and yhat (  default  )
##   -> residuals         :  numerical, min =  -0.9750531 , mean =  -0.00000002748073 , max =  0.9764736  
##   A new explainer has been created! 
rf_explain <- 
  explain_tidymodels(
    model = lc_rf_final_mod,
    data = lc_training %>% select(-Class), 
    y = lc_training %>% mutate(Class_num = as.integer(Class =="good")) %>% pull(Class_num),
    label = "rf",
    type = "classification"
  )
## Preparation of a new explainer is initiated
##   -> model label       :  rf 
##   -> data              :  9643  rows  22  cols 
##   -> data              :  tibble converted into a data.frame 
##   -> target variable   :  9643  values 
##   -> predict function  :  yhat.workflow  will be used (  default  )
##   -> predicted values  :  No value for predict function target column. (  default  )
##   -> model_info        :  package tidymodels , ver. 0.1.2 , task classification (  default  ) 
##   -> model_info        :  type set to  classification 
##   -> predicted values  :  numerical, min =  0 , mean =  0.7013818 , max =  1  
##   -> residual function :  difference between y and yhat (  default  )
##   -> residuals         :  numerical, min =  -0.43 , mean =  0.02442964 , max =  0.35  
##   A new explainer has been created! 
# get model evaluation metrics
lasso_perf <- model_performance(lasso_explain, type = "classification")
rf_perf <- model_performance(rf_explain)

# make the plots
hist_plot <- plot(rf_perf, lasso_perf, geom = "histogram")
box_plot <- plot(lasso_perf, rf_perf, geom = "boxplot")

hist_plot + box_plot

Between the random forest model and LASSO, the residuals from the former look much better. They center around 0, have a smaller spread, and the RMSE is very close to 0.

  1. Use DALEX functions to create a variable importance plot from this model. What are the most important variables?
set.seed(494) 

lasso_var_imp <- model_parts(lasso_explain)
rf_var_imp <- model_parts(rf_explain)
plot(lasso_var_imp, show_boxplots = TRUE) + plot(rf_var_imp, show_boxplots = TRUE)

The four most important variables from LASSO are int_rate, total_bal_il, total_il_high_credit_limit.

For random forest, they are int_rate, sub_grade, annual_inc, open_il_12m.

  1. Write a function called cp_profile to make a CP profile. The function will take an explainer, a new observation, and a variable name as its arguments and create a CP profile for a quantitative predictor variable. You will need to use the predict_profile() function inside the function you create - put the variable name there so the plotting part is easier. You’ll also want to use aes_string() rather than aes() and quote the variables. Use the cp_profile() function to create one CP profile of your choosing. Be sure to choose a variable that is numeric, not integer. There seem to be issues with those that I’m looking into.
cp_profile <- function(explainer, new_obs, var_name) {
  cpp <- predict_profile(explainer = explainer, new_observation = new_obs, variables = var_name) 
  
  cpp %>%
    rename(yhat = `_yhat_`) %>%
    filter(`_vname_` %in% c(var_name)) %>%
    ggplot(aes_string(x = var_name, y = "yhat")) +
    geom_line()
}
set.seed(494)
cp_profile(rf_explain, lc_training %>% sample_n(size = 1), "int_rate")

For an extra challenge, write a function that will work for either a quantitative or categorical variable.

If you need help with function writing check out the Functions chapter of R4DS by Wickham and Grolemund.

  1. Use DALEX functions to create partial dependence plots (with the CP profiles in gray) for the 3-4 most important variables. If the important variables are categorical, you can instead make a CP profile for 3 observations in the dataset and discuss how you could go about constructing a partial dependence plot for a categorical variable (you don’t have to code it, but you can if you want an extra challenge). If it ever gives you an error that says, “Error: Can’t convert from VARIABLE to VARIABLE due to loss of precision”, then remove that variable from the list. I seem to have figured out why it’s doing that, but I don’t know how to fix it yet.
set.seed(494) 
rf_pdp <- model_profile(
  explainer = rf_explain,
  variables = c("int_rate", "annual_inc", "open_il_12m"))

plot(rf_pdp, 
     variables = c("int_rate", "annual_inc", "open_il_12m"),
     geom = "profiles")

  1. Fit one more model type of your choosing that will feed into the stacking model.
# create a model definition
lc_knn_mod <-
  nearest_neighbor(
    neighbors = tune("k")
  ) %>%
  set_engine("kknn") %>% 
  set_mode("classification")

# create the workflow
lc_knn_wf <- 
  workflow() %>% 
  add_model(lc_knn_mod) %>%
  add_recipe(lc_lasso_recipe)

# tune it using 4 tuning parameters
lc_knn_tune <- 
  lc_knn_wf %>% 
  tune_grid(
    lc_cv,
    metrics = metric,
    grid = 4,
    control = ctrl_grid
  )
  1. Create a model stack with the candidate models from the previous parts of the exercise and use the blend_predictions() function to find the coefficients of the stacked model. Create a plot examining the performance metrics for the different penalty parameters to assure you have captured the best one. If not, adjust the penalty. (HINT: use the autoplot() function). Which models are contributing most?
lc_stack <- 
  stacks() %>% 
  add_candidates(lc_rf_tune) %>% 
  add_candidates(lc_lasso_tune) %>% 
  add_candidates(lc_knn_tune)
(lc_blend <- 
   lc_stack %>%
   blend_predictions())
## # A tibble: 2 x 3
##   member                    type        weight
##   <chr>                     <chr>        <dbl>
## 1 .pred_good_lc_rf_tune_1_2 rand_forest  4.75 
## 2 .pred_good_lc_rf_tune_1_3 rand_forest  0.158
autoplot(lc_blend)

Random forest is the only model that was retained. I got varying results by changing the parameters to the previous tuning steps but overall it seems like random forest always came out on top with the greatest weight. The coefficients on the other models when they were included were very small (around or less than 1). This also made the final model smaller in terms of file size because it could exceed 100 MB and if so then I wouldn’t be able to upload to Github.

  1. Fit the final stacked model using fit_members(). Apply the model to the test data and report the accuracy and area under the curve. Create a graph of the ROC and construct a confusion matrix. Comment on what you see. Save this final model using the saveRDS() function - see the Use the model section of the tidymodels intro. We are going to use the model in the next part. You’ll want to save it in the folder where you create your shiny app.
(lc_final_stack <- lc_blend %>%
   fit_members)
## # A tibble: 2 x 3
##   member                    type        weight
##   <chr>                     <chr>        <dbl>
## 1 .pred_good_lc_rf_tune_1_2 rand_forest  4.75 
## 2 .pred_good_lc_rf_tune_1_3 rand_forest  0.158
# make predictions on test data
(lc_stack_preds <- 
    cbind(lc_final_stack %>% predict(new_data = lc_testing),
          lc_final_stack %>% predict(new_data = lc_testing, type = "prob")) %>%
    bind_cols(lc_testing) %>%
    relocate(Class, .after = .pred_good))
# ROC AUC
lc_stack_preds %>%
  # the Class level is (bad, good) so we need to specify that the event is the second one
  roc_auc(.pred_good, truth = Class, event_level = "second")
# accuracy
lc_stack_preds %>%
  # the Class level is (bad, good) so we need to specify that the event is the second one
  accuracy(.pred_class, truth = Class, event_level = "second")
# graph of ROC
lc_stack_preds %>%
  # the Class level is (bad, good) so we need to specify that the event is the second one
  roc_curve(.pred_good, truth = Class, event_level = "second") %>%
  ggplot(aes(x = 1 - specificity, y = sensitivity)) +
  geom_path() +
  geom_abline(lty = 3) +
  coord_equal() +
  labs(y = "True Positive Rate (Sensitivity)",
       x = "False Positive Rate",
       fill = NULL,
       title = "ROC Curve",
       subtitle = "Stack Model")

# confusion matrix
lc_stack_preds %>%
  conf_mat(Class, .pred_class) %>%
  pluck(1) %>%
  as_tibble() %>%
  ggplot(aes(Prediction, Truth, alpha = n)) +
  geom_tile(show.legend = FALSE) +
  geom_text(aes(label = n), colour = "white", alpha = 1, size = 8) +
  theme(panel.grid.major = element_blank()) +
  labs(y = "Actual Class",
       x = "Predicted Class",
       fill = NULL,
       title = "Confusion Matrix",
       subtitle = "Stack Model")

The ideal ROC curve is one that goes high up the upper-left corner and that is the behavior we see in the plot. On top of this, almost all of the observations in the test data were classified correctly.

saveRDS(lc_final_stack, "lending_club_stack_fit.rds")

Shiny app

If you are new to Shiny apps or it’s been awhile since you’ve made one, visit the Shiny links on our course Resource page. I would recommend starting with my resource because it will be the most basic. You won’t be doing anything super fancy in this app.

Everyone should watch the Theming Shiny talk by Carson Sievert so you can make your app look amazing.

Tasks:

You are going to create an app that allows a user to explore how the predicted probability of a loan being paid back (or maybe just the predicted class - either “good” or “bad”) changes depending on the values of the predictor variables.

Specifically, you will do the following:

  • Set up a separate project and GitHub repo for this app. Make sure the saved model from the previous problem is also in that folder. The app needs to be created in a file called exactly app.R that is also in the project folder.
  • At the top of the file, load any libraries you use in the app.
  • Use the readRDS() function to load the model.
  • You may want to load some of the data to use
  • Create a user interface (using the various *Input() functions) where someone could enter values for each variable that feeds into the model. You will want to think hard about which types of *Input() functions to use. Think about how you can best prevent mistakes (eg. entering free text could lead to many mistakes).
  • Another part of the user interface will allow them to choose a variable (you can limit this to only the quantitative variables) where they can explore the effects of changing that variable, holding all others constant.
  • After the user has entered all the required values, the output will be a CP profile with the the predicted value for the data that was entered, indicated by a point. I don’t think the functions from DALEX and DALEXtra will work with a stacked model, so you’ll likely have to (get to) do some of your own coding.
  • Use the bslib to theme your shiny app!
  • Publish your app to shinyapps.io. There are instructions for doing that on the tutorial I linked to above.
  • Write a paragraph or two describing your app on your website! Link to the app and your GitHub repository in your post. Include a link to your post here.

Coded Bias

Watch the Code Bias film and write a short reflection. If you want some prompts, reflect on: What part of the film impacted you the most? Was there a part that surprised you and why? What emotions did you experience while watching?

I thought that Coded Bias was excellent. It covers all of the most pressing issues posed by AI technology that we currently face in a way that was very digestible. There was nothing overly technical about the discussions that were brought up, which made the film approachable to a wide audience beyond those who work in tech. I think this is crucial as these types of software are being deployed on a large scale in a wide variety of fields. As they have access to people’s data and make decisions that can have very serious consequences, people first need to be made aware that their privacy and data are being monitored and potentially exploited. It is one thing for a person to be less careful about their online presence because a company might be tracking their activity, but it becomes very serious the outcomes of their job application, health insurance, credit score, etc. are being decided by an essentially black-box process and that they have no idea this is what happens behind the scenes. If they do not know and blindly accept these decisions then how can they appeal when the system is inaccurate? Personally, what’s more troubling is that even the people behind these algorithms cannot fully explain how they arrived at those decisions on a case-by-case basis. This is something that was voiced by many speakers in the movie so it truly is alarming how these tools were deployed without first being thoroughly evaluated and tested by parties that are knowledgeable about the subject at hand.

This is where guidelines and regulations really need to come in. First and foremost we need to limit the amount and kind of data companies and the state can collect and share and to be transparent about data collection and usage. For example in the case of facial recognition technology, people should not be screened without knowledge let alone consent when there is no evidence of them having violated something. People need to know what their personal data is being used for and who potentially stands to profit from it. Furthermore, when it comes to algorithms, although many of them are proprietary, the government and other monitoring agencies can still enforce regulations and evaluation processes that they must comply with before being deployed. Along the same lines, decisions made by algorithms need to be transparent and come with an explanation. People have the right to know why and evidently the agencies using these algorithms also need to fully understand the decisions they ultimately authorize. If left unchecked then we are going down this path where, as one of the speakers said, we would be implicitly nudged and influenced by decisions made by corporations and governments that are not always acting in our best interests. Moreover, we need to address how these issues disproportionately affect marginalized or targeted groups. These algorithms not only run on data but also produce data as they are making predictions, classifications, etc. so they can seriously aggravate injustices and inequities.

Ultimately, I do believe that technology is powerful and can advance and revolutionize our lives but we need to ensure that the benefits and progress are equitably distributed to all members of society. Of course the public need to be aware but they can only know to an extent. People working in AI, researchers, and policy-makers should shoulder most of the responsibility for creating, evaluating, and monitoring these tools to ensure that their use will be appropriate and just.

REMEMBER TO ADD YOUR GITHUB LINK AT THE TOP OF THE PAGE AND UNCOMMENT THE knitr OPTIONS.

LS0tCnRpdGxlOiAnQXNzaWdubWVudCAjMicKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbiA9ICJjZW50ZXIiLCBjYWNoZT1UUlVFKQpgYGAKCmBgYHtyIGxpYnJhcmllc30KIyBTRUUgbW9kZWxkYXRhIHBhY2thZ2UgZm9yIG5ldyBkYXRhc2V0cwpsaWJyYXJ5KHhsc3gpCmxpYnJhcnkodGlkeXZlcnNlKSAgICAgICAgICMgZm9yIGdyYXBoaW5nIGFuZCBkYXRhIGNsZWFuaW5nCmxpYnJhcnkodGlkeW1vZGVscykgICAgICAgICMgZm9yIG1vZGVsaW5nCmxpYnJhcnkoc3RhY2tzKSAgICAgICAgICAgICMgZm9yIHN0YWNraW5nIG1vZGVscwpsaWJyYXJ5KG5hbmlhcikgICAgICAgICAgICAjIGZvciBleGFtaW5pbmcgbWlzc2luZyB2YWx1ZXMgKE5BcykKbGlicmFyeShsdWJyaWRhdGUpICAgICAgICAgIyBmb3IgZGF0ZSBtYW5pcHVsYXRpb24KbGlicmFyeShtb2Rlcm5kaXZlKSAgICAgICAgIyBmb3IgS2luZyBDb3VudHkgaG91c2luZyBkYXRhCmxpYnJhcnkodmlwKSAgICAgICAgICAgICAgICMgZm9yIHZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdHMKbGlicmFyeShEQUxFWCkgICAgICAgICAgICAgIyBmb3IgbW9kZWwgaW50ZXJwcmV0YXRpb24gIApsaWJyYXJ5KERBTEVYdHJhKSAgICAgICAgICAjIGZvciBleHRlbnNpb24gb2YgREFMRVgKbGlicmFyeShwYXRjaHdvcmspICAgICAgICAgIyBmb3IgY29tYmluaW5nIHBsb3RzIG5pY2VseQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKSAjIExpc2EncyBmYXZvcml0ZSB0aGVtZQpvcHRpb25zKHNjaXBlbj05OTkpCmBgYAoKYGBge3IgZGF0YX0KZGF0YSgibGVuZGluZ19jbHViIikKIyBEYXRhIGRpY3Rpb25hcnkgKGFzIGNsb3NlIGFzIEkgY291bGQgZmluZCk6IGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vd29yZHNmb3J0aGV3aXNlL2xlbmRpbmctY2x1Yi9kaXNjdXNzaW9uLzE3MDY5MQpgYGAKCgpXaGVuIHlvdSBmaW5pc2ggdGhlIGFzc2lnbm1lbnQsIHJlbW92ZSB0aGUgYCNgIGZyb20gdGhlIG9wdGlvbnMgY2h1bmsgYXQgdGhlIHRvcCwgc28gdGhhdCBtZXNzYWdlcyBhbmQgd2FybmluZ3MgYXJlbid0IHByaW50ZWQuIElmIHlvdSBhcmUgZ2V0dGluZyBlcnJvcnMgaW4geW91ciBjb2RlLCBhZGQgYGVycm9yID0gVFJVRWAgc28gdGhhdCB0aGUgZmlsZSBrbml0cy4gSSB3b3VsZCByZWNvbW1lbmQgbm90IHJlbW92aW5nIHRoZSBgI2AgdW50aWwgeW91IGFyZSBjb21wbGV0ZWx5IGZpbmlzaGVkLgoKIyMgUHV0IGl0IG9uIEdpdEh1YiEgICAgICAgIAoKRnJvbSBub3cgb24sIEdpdEh1YiBzaG91bGQgYmUgcGFydCBvZiB5b3VyIHJvdXRpbmUgd2hlbiBkb2luZyBhc3NpZ25tZW50cy4gSSByZWNvbW1lbmQgbWFraW5nIGl0IHBhcnQgb2YgeW91ciBwcm9jZXNzIGFueXRpbWUgeW91IGFyZSB3b3JraW5nIGluIFIsIGJ1dCBJJ2xsIG1ha2UgeW91IHNob3cgaXQncyBwYXJ0IG9mIHlvdXIgcHJvY2VzcyBmb3IgYXNzaWdubWVudHMuCgoqKlRhc2sqKjogV2hlbiB5b3UgYXJlIGZpbmlzaGVkIHdpdGggdGhlIGFzc2lnbm1lbnQsIHBvc3QgYSBsaW5rIGJlbG93IHRvIHRoZSBHaXRIdWIgcmVwbyBmb3IgdGhlIGFzc2lnbm1lbnQuIElmIHlvdSB3YW50IHRvIHBvc3QgaXQgdG8geW91ciBwZXJzb25hbCB3ZWJzaXRlLCB0aGF0J3Mgb2sgKG5vdCByZXF1aXJlZCkuIE1ha2Ugc3VyZSB0aGUgbGluayBnb2VzIHRvIGEgc3BvdCBpbiB0aGUgcmVwbyB3aGVyZSBJIGNhbiBlYXNpbHkgZmluZCB0aGlzIGFzc2lnbm1lbnQuIEZvciBleGFtcGxlLCBpZiB5b3UgaGF2ZSBhIHdlYnNpdGUgd2l0aCBhIGJsb2cgYW5kIHBvc3QgdGhlIGFzc2lnbm1lbnQgYXMgYSBibG9nIHBvc3QsIGxpbmsgdG8gdGhlIHBvc3QncyBmb2xkZXIgaW4gdGhlIHJlcG8uIEFzIGFuIGV4YW1wbGUsIEkndmUgbGlua2VkIHRvIG15IEdpdEh1YiBzdGFja2luZyBtYXRlcmlhbCBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2xsZW5kd2F5L2Fkc193ZWJzaXRlL3RyZWUvbWFzdGVyL19wb3N0cy8yMDIxLTAzLTIyLXN0YWNraW5nKS4KCmh0dHBzOi8vZ2l0aHViLmNvbS90aHl0bmcvc3RhdDQ5NC1hc3NpZ25tZW50MgoKIyMgTW9kZWxpbmcKCkJlZm9yZSBqdW1waW5nIGludG8gdGhlc2UgcHJvYmxlbXMsIHlvdSBzaG91bGQgcmVhZCB0aHJvdWdoIChhbmQgZm9sbG93IGFsb25nIHdpdGghKSB0aGUgW21vZGVsIHN0YWNraW5nXShodHRwczovL2FkdmFuY2VkLWRzLWluLXIubmV0bGlmeS5hcHAvcG9zdHMvMjAyMS0wMy0yMi1zdGFja2luZy8pIGFuZCBbZ2xvYmFsIG1vZGVsIGludGVycHJldGF0aW9uXShodHRwczovL2FkdmFuY2VkLWRzLWluLXIubmV0bGlmeS5hcHAvcG9zdHMvMjAyMS0wMy0yNC1pbWxnbG9iYWwvKSB0dXRvcmlhbHMgb24gdGhlIENvdXJzZSBNYXRlcmlhbHMgdGFiIG9mIHRoZSBjb3Vyc2Ugd2Vic2l0ZS4KCldlJ2xsIGJlIHVzaW5nIHRoZSBgbGVuZGluZ19jbHViYCBkYXRhc2V0IGZyb20gdGhlIGBtb2RlbGRhdGFgIGxpYnJhcnksIHdoaWNoIGlzIHBhcnQgb2YgYHRpZHltb2RlbHNgLiBUaGUgZGF0YSBkaWN0aW9uYXJ5IHRoZXkgcmVmZXJlbmNlIGRvZXNuJ3Qgc2VlbSB0byBleGlzdCBhbnltb3JlLCBidXQgaXQgc2VlbXMgdGhlIG9uZSBvbiB0aGlzIFtrYWdnbGUgZGlzY3Vzc2lvbl0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS93b3Jkc2ZvcnRoZXdpc2UvbGVuZGluZy1jbHViL2Rpc2N1c3Npb24vMTcwNjkxKSBpcyBwcmV0dHkgY2xvc2UuIEl0IG1pZ2h0IGFsc28gaGVscCB0byByZWFkIGEgYml0IGFib3V0IFtMZW5kaW5nIENsdWJdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0xlbmRpbmdDbHViKSBiZWZvcmUgc3RhcnRpbmcgaW4gb24gdGhlIGV4ZXJjaXNlcy4KClRoZSBvdXRjb21lIHdlIGFyZSBpbnRlcmVzdGVkIGluIHByZWRpY3RpbmcgaXMgYENsYXNzYC4gQW5kIGFjY29yZGluZyB0byB0aGUgZGF0YXNldCdzIGhlbHAgcGFnZSwgaXRzIHZhbHVlcyBhcmUgImVpdGhlciAnZ29vZCcgKG1lYW5pbmcgdGhhdCB0aGUgbG9hbiB3YXMgZnVsbHkgcGFpZCBiYWNrIG9yIGN1cnJlbnRseSBvbi10aW1lKSBvciAnYmFkJyAoY2hhcmdlZCBvZmYsIGRlZmF1bHRlZCwgb2YgMjEtMTIwIGRheXMgbGF0ZSkiLgoKKipUYXNrczoqKiBJIHdpbGwgYmUgZXhwYW5kaW5nIHRoZXNlLCBidXQgdGhpcyBnaXZlcyBhIGdvb2Qgb3V0bGluZS4KCjEuIEV4cGxvcmUgdGhlIGRhdGEsIGNvbmNlbnRyYXRpbmcgb24gZXhhbWluaW5nIGRpc3RyaWJ1dGlvbnMgb2YgdmFyaWFibGVzIGFuZCBleGFtaW5pbmcgbWlzc2luZyB2YWx1ZXMuIAoKKipMb2FkaW5nIHRoZSBkYXRhIGRpY3Rpb25hcnkgZG93bmxvYWRlZCBmcm9tIEthZ2dsZToqKgoKYGBge3IgZGljdH0KKGRpY3QgPC0gcmVhZC54bHN4KCJMQ0RhdGFEaWN0aW9uYXJ5Lnhsc3giLCBzaGVldEluZGV4ID0gMSlbLDE6Ml0gJT4lCiAgIGZpbHRlcihMb2FuU3RhdE5ldyAlaW4lIG5hbWVzKGxlbmRpbmdfY2x1YikpKSAKYGBgCioqRXhwbG9yaW5nIG51bWVyaWNhbCB2YXJpYWJsZXM6KioKCmBgYHtyIGV4cGwtcXVhbnQsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTZ9CmxlbmRpbmdfY2x1YiAlPiUKICBzZWxlY3Qod2hlcmUoaXMubnVtZXJpYykpICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLAogICAgICAgICAgICAgICBuYW1lc190byA9ICJ2YXJpYWJsZSIsCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWx1ZXMiKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZXMpKSArCiAgZ2VvbV9oaXN0b2dyYW0oKSArCiAgZmFjZXRfd3JhcCh2YXJzKHZhcmlhYmxlKSwKICAgICAgICAgICAgIHNjYWxlcyA9ICJmcmVlIikKYGBgCgoqKkNhdGVnb3JpY2FsIHZhcmlhYmxlczoqKgoKYGBge3IgZXhwbC1jYXQsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTZ9CmxlbmRpbmdfY2x1YiAlPiUKICBzZWxlY3QoaXMuZmFjdG9yKSAlPiUKICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGUiLAogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZSkpICsKICBnZW9tX2JhcigpICsKICBmYWNldF93cmFwKH52YXJpYWJsZSwgCiAgICAgICAgICAgICBzY2FsZSA9ICJmcmVlIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMzAsIGhqdXN0ID0gMSwgc2l6ZSA9IDYpKQpgYGAKCioqQ2hlY2tpbmcgZm9yIG1pc3NpbmcgZGF0YToqKgoKYGBge3IgbWlzc2luZ30KbGVuZGluZ19jbHViICU+JQogIGFkZF9uX21pc3MoKSAlPiUKICBjb3VudChuX21pc3NfYWxsKQpgYGAKCgoyLiBEbyBhbnkgZGF0YSBjbGVhbmluZyBzdGVwcyB0aGF0IG5lZWQgdG8gaGFwcGVuIGJlZm9yZSB0aGUgbW9kZWwgaXMgYnVpbGQuIEZvciBleGFtcGxlLCB5b3UgbWlnaHQgcmVtb3ZlIGFueSB2YXJpYWJsZXMgdGhhdCBtZWFuIHRoZSBzYW1lIHRoaW5nIGFzIHRoZSByZXNwb25zZSB2YXJpYWJsZSAobm90IHN1cmUgaWYgdGhhdCBoYXBwZW5zIGhlcmUpLCBnZXQgcmlkIG9mIHJvd3Mgd2hlcmUgYWxsIHZhcmlhYmxlcyBoYXZlIG1pc3NpbmcgdmFsdWVzLCBldGMuIAoKKipUaGVyZSdzIG5vIG1pc3NpbmcgLyBudWxsIGRhdGEgYW5kIEkgY291bGRuJ3QgZmluZCBhbnkgcmVkdW5kYW50IHZhcmlhYmxlcy4gYGVtcF9sZW5ndGhgIGlzIGEgYml0IHRyaWNreSBiZWNhdXNlIGl0IHNob3VsZCBiZSB0aGUgZW1wbG95bWVudCBsZW5ndGggaW4geWVhcnMgYnV0IHRoZSB2YXJpYWJsZSBpcyBjYXRlZ29yaWNhbCBpbiB0aGUgZGF0YS4gSSBjYW4gbWFrZSBpdCBjb250aW51b3VzIGJ1dCB0aGVyZSBhcmUgY2F0ZWdvcmllcyBmb3IgbGVzcyB0aGFuIDEgeWVhciwgbW9yZSB0aGFuIDkgeWVhcnMsIGFuZCB1bmtub3duIHNvIGl0J3MgcHJvYmFibHkgYmVzdCB0byBsZWF2ZSBpdCB1bnRvdWNoZWQuKioKCkJlIHN1cmUgdG8gYWRkIG1vcmUgImJhZCIgQ2xhc3Nlcy4gVGhpcyBpcyBub3QgdGhlIGJlc3Qgc29sdXRpb24sIGJ1dCBpdCB3aWxsIHdvcmsgZm9yIG5vdy4gKFNob3VsZCBpbnZlc3RpZ2F0ZSBob3cgdG8gYXBwcm9wcmlhdGVseSB1c2UgYHN0ZXBfc2FtcGxlX3VwKClgIGZ1bmN0aW9uIGZyb20gW2B0aGVtaXNgXShodHRwczovL2dpdGh1Yi5jb20vdGlkeW1vZGVscy90aGVtaXMpKS4KCmBgYHtyfQpjcmVhdGVfbW9yZV9iYWQgPC0gbGVuZGluZ19jbHViICU+JSAKICBmaWx0ZXIoQ2xhc3MgPT0gImJhZCIpICU+JSAKICBzYW1wbGVfbihzaXplID0gMzAwMCwgcmVwbGFjZSA9IFRSVUUpCgpsZW5kaW5nX2NsdWJfbW9kIDwtIGxlbmRpbmdfY2x1YiAlPiUgCiAgYmluZF9yb3dzKGNyZWF0ZV9tb3JlX2JhZCkKYGBgCgozLiBTcGxpdCB0aGUgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0LCBwdXR0aW5nIDc1XCUgaW4gdGhlIHRyYWluaW5nIGRhdGEuCgpgYGB7ciBpbml0LXNwbGl0fQpzZXQuc2VlZCg0OTQpICMgZm9yIHJlcHJvZHVjaWJpbGl0eQpsY19zcGxpdCA8LSBpbml0aWFsX3NwbGl0KGxlbmRpbmdfY2x1Yl9tb2QsIHByb3AgPSAuNzUpCmxjX3RyYWluaW5nIDwtIHRyYWluaW5nKGxjX3NwbGl0KQpsY190ZXN0aW5nIDwtIHRlc3RpbmcobGNfc3BsaXQpCmBgYAoKNC4gU2V0IHVwIHRoZSByZWNpcGUgYW5kIHRoZSBwcmUtcHJvY2Vzc2luZyBzdGVwcyB0byBidWlsZCBhIGxhc3NvIG1vZGVsLiBTb21lIHN0ZXBzIHlvdSBzaG91bGQgdGFrZToKCiogTWFrZSBhbGwgaW50ZWdlciB2YXJpYWJsZXMgbnVtZXJpYyAoSSdkIGhpZ2hseSByZWNvbW1lbmQgdXNpbmcgYHN0ZXBfbXV0YXRlX2F0KClgIG9yIHRoaXMgd2lsbCBiZSBhIGxvdCBvZiBjb2RlKS4gV2UnbGwgd2FudCB0byBkbyB0aGlzIGZvciB0aGUgbW9kZWwgaW50ZXJwcmV0YXRpb24gd2UnbGwgZG8gbGF0ZXIuICAKKiBUaGluayBhYm91dCBncm91cGluZyBmYWN0b3IgdmFyaWFibGVzIHdpdGggbWFueSBsZXZlbHMuICAKKiBNYWtlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBkdW1teSB2YXJpYWJsZXMgKG1ha2Ugc3VyZSBOT1QgdG8gZG8gdGhpcyB0byB0aGUgb3V0Y29tZSB2YXJpYWJsZSkuICAKKiBOb3JtYWxpemUgcXVhbnRpdGF0aXZlIHZhcmlhYmxlcy4gIAoKYGBge3IgbGFzc28tcmVjaXBlfQpsY19sYXNzb19yZWNpcGUgPC0gcmVjaXBlKENsYXNzIH4gLiwgZGF0YSA9IGxlbmRpbmdfY2x1Yl9tb2QpICU+JQogICMgY29udmVydCBhbGwgaW50ZWdlcnMgdG8gbnVtZXJpY3MKICBzdGVwX211dGF0ZV9hdChpcy5pbnRlZ2VyLCBmbiA9IGFzLm51bWVyaWMpICU+JQogIHN0ZXBfbXV0YXRlKAogICAgIyBtZXJnZSBhbGwgc3ViZ3JhZGVzIHdpdGggdGhlIHNhbWUgbGV0dGVyIGdyYWRlIGludG8gb25lIGNhdGVnb3J5CiAgICBncmFkZSA9IHN1YnN0cihzdWJfZ3JhZGUsIDAsIDEpLAogICAgIyBjb252ZXJ0IGFkZHJlc3Mgc3RhdGUgdG8gYWRkcmVzcyByZWdpb24KICAgIGFkZHJfcmVnaW9uID0gZmN0X3JlbGV2ZWwoCiAgICAgIGNhc2Vfd2hlbigKICAgICAgICBhZGRyX3N0YXRlICVpbiUgYygiQ1QiLCAiTUEiLCAiTUUiLCAiTkgiLCAiUkkiLCAiVlQiKSB+ICJOb3J0aGVhc3QiLAogICAgICAgIGFkZHJfc3RhdGUgJWluJSBjKCJERSIsICJNRCIsICJOSiIsICJOWSIsICJQQSIsICJEQyIpIH4gIk1pZC1BdGxhbnRpYyIsCiAgICAgICAgYWRkcl9zdGF0ZSAlaW4lIGMoIkFMIiwgIkFSIiwgIkZMIiwgIkdBIiwgIktZIiwgIkxBIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIk1TIiwgIk5DIiwgIlNDIiwgIlROIiwgIlZBIiwgIldWIikgfiAiU291dGgiLAogICAgICAgIGFkZHJfc3RhdGUgJWluJSBjKCJJQSIsICJJTCIsICJJTiIsICJLUyIsICJNSSIsICJNTiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICJNTyIsICJORCIsICJORSIsICJPSCIsICJTRCIsICJXSSIpIH4gIk1pZHdlc3QiLAogICAgICAgIGFkZHJfc3RhdGUgJWluJSBjKCJBSyIsICJDQSIsICJDTyIsICJISSIsICJJRCIsICJNVCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICJOViIsICJPUiIsICJVVCIsICJXQSIsICJXWSIpIH4gIldlc3QiLAogICAgICAgIGFkZHJfc3RhdGUgJWluJSBjKCJBWiIsICJOTSIsICJPSyIsICJUWCIpIH4gIlNvdXRod2VzdCIKICAgICAgICApKSkgJT4lCiAgIyBhZGQgYSBjb25zdGFudCB0byB0aGUgdmFyaWFibGVzIHdlIHdpbGwgbG9nLXRyYW5zZm9ybSBiZWNhdXNlIHRoZXJlIGFyZSAwcyBpbiB0aGUgZGF0YQogIHN0ZXBfbXV0YXRlX2F0KGFubnVhbF9pbmMsIGRlbGlucV9hbW50LCB0b3RhbF9iYWxfaWwsIHRvdGFsX2lsX2hpZ2hfY3JlZGl0X2xpbWl0LCAKICAgICAgICAgICAgICAgICBmbiA9IGZ1bmN0aW9uKHgpIHgrMSkgJT4lCiAgIyBsb2cgdHJhbnNmb3JtIHdpdGggYmFzZSAxMAogIHN0ZXBfbG9nKGFubnVhbF9pbmMsIGRlbGlucV9hbW50LCB0b3RhbF9iYWxfaWwsIHRvdGFsX2lsX2hpZ2hfY3JlZGl0X2xpbWl0LCBiYXNlID0gMTApICU+JQogICMgcmVtb3ZlIHRoZSBvbGQgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHRoYXQgd2UgcmVmYWN0b3JlZAogIHN0ZXBfcm0oc3ViX2dyYWRlLCBhZGRyX3N0YXRlKSAlPiUKICBzdGVwX211dGF0ZV9hdChncmFkZSwgZm4gPSBhcy5mYWN0b3IpICU+JQogICMgY3JlYXRlIGR1bW15IHZhcmlhYmxlcyBmb3IgY2F0ZWdvcmljYWwgdmFycwogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUKICAjIG5vcm1hbGl6ZSBxdWFudGl0YXRpdmUgdmFycwogIHN0ZXBfbm9ybWFsaXplKGFsbF9wcmVkaWN0b3JzKCksIC1hbGxfbm9taW5hbCgpKSAKYGBgCgpgYGB7cn0KbGNfbGFzc29fcmVjaXBlICU+JQogIHByZXAobGNfdHJhaW5pbmcpICU+JQogIGp1aWNlKCkgCmBgYAoKCjUuIFNldCB1cCB0aGUgbGFzc28gbW9kZWwgYW5kIHdvcmtmbG93LiBXZSB3aWxsIHR1bmUgdGhlIGBwZW5hbHR5YCBwYXJhbWV0ZXIuCgpgYGB7ciBsYXNzby1tb2R9CmxjX2xhc3NvX21vZCA8LSAKICBsb2dpc3RpY19yZWcobWl4dHVyZSA9IDEpICU+JQogIHNldF9lbmdpbmUoImdsbW5ldCIpICU+JQogIHNldF9hcmdzKHBlbmFsdHkgPSB0dW5lKCkpICU+JQogIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpCmBgYAoKYGBge3IgbGFzc28td2Z9CmxjX2xhc3NvX3dmIDwtIHdvcmtmbG93KCkgJT4lCiAgYWRkX3JlY2lwZShsY19sYXNzb19yZWNpcGUpICU+JQogIGFkZF9tb2RlbChsY19sYXNzb19tb2QpCmBgYAoKCjYuIFNldCB1cCB0aGUgbW9kZWwgdHVuaW5nIGZvciB0aGUgYHBlbmFsdHlgIHBhcmFtZXRlci4gQmUgc3VyZSB0byBhZGQgdGhlIGBjb250cm9sX3N0YWNrX2dyaWQoKWAgZm9yIHRoZSBgY29udHJvbGAgYXJndW1lbnQgc28gd2UgY2FuIHVzZSB0aGVzZSByZXN1bHRzIGxhdGVyIHdoZW4gd2Ugc3RhY2suIEZpbmQgdGhlIGFjY3VyYWN5IGFuZCBhcmVhIHVuZGVyIHRoZSByb2MgY3VydmUgZm9yIHRoZSBtb2RlbCB3aXRoIHRoZSBiZXN0IHR1bmluZyBwYXJhbWV0ZXIuIFVzZSA1LWZvbGQgY3YuCgpgYGB7ciBsYXNzby10dW5lfQpzZXQuc2VlZCg0OTQpIApwZW5hbHR5X2dyaWQgPC0gZ3JpZF9yZWd1bGFyKHBlbmFsdHkoKSwgbGV2ZWxzID0gNCkKY3RybF9ncmlkIDwtIGNvbnRyb2xfc3RhY2tfZ3JpZCgpCm1ldHJpYyA8LSBtZXRyaWNfc2V0KHJvY19hdWMpCmxjX2N2IDwtIHZmb2xkX2N2KGxjX3RyYWluaW5nLCB2ID0gNSkKCmxjX2xhc3NvX3R1bmUgPC0gbGNfbGFzc29fd2YgJT4lCiAgdHVuZV9ncmlkKHJlc2FtcGxlcyA9IGxjX2N2LAogICAgICAgICAgICBncmlkID0gcGVuYWx0eV9ncmlkLAogICAgICAgICAgICBjb250cm9sID0gY3RybF9ncmlkLCAKICAgICAgICAgICAgbWV0cmljcyA9IG1ldHJpYykKYGBgCgoKNy4gU2V0IHVwIHRoZSByZWNpcGUgYW5kIHRoZSBwcmUtcHJvY2Vzc2luZyBzdGVwcyB0byBidWlsZCBhIHJhbmRvbSBmb3Jlc3QgbW9kZWwuIFlvdSBzaG91bGRuJ3QgaGF2ZSB0byBkbyBhcyBtYW55IHN0ZXBzLiBUaGUgb25seSBzdGVwIHlvdSBzaG91bGQgbmVlZCB0byBkbyBpcyBtYWtpbmcgYWxsIGludGVnZXJzIG51bWVyaWMuIAoKYGBge3IgcmYtcmVjaXBlfQpsY19yZl9yZWNpcGUgPC0gcmVjaXBlKENsYXNzIH4gLiwgZGF0YSA9IGxlbmRpbmdfY2x1Yl9tb2QpICU+JQogIHN0ZXBfbXV0YXRlX2F0KGlzLmludGVnZXIsIGZuID0gYXMubnVtZXJpYykgCmBgYAoKOC4gU2V0IHVwIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsIGFuZCB3b3JrZmxvdy4gV2Ugd2lsbCB0dW5lIHRoZSBgbXRyeWAgYW5kIGBtaW5fbmAgcGFyYW1ldGVycyBhbmQgc2V0IHRoZSBudW1iZXIgb2YgdHJlZXMsIGB0cmVlc2AsIHRvIDEwMCAob3RoZXJ3aXNlIHRoZSBuZXh0IHN0ZXBzIHRha2UgdG9vIGxvbmcpLgoKYGBge3IgcmYtbW9kfQpsY19yZl9tb2QgPC0gcmFuZF9mb3Jlc3QobXRyeSA9IHR1bmUoKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fbiA9IHR1bmUoKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICB0cmVlcyA9IDEwMCkgJT4lIAogIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpICU+JSAKICBzZXRfZW5naW5lKCJyYW5nZXIiKQpgYGAKCmBgYHtyIHJmLXdmfQpsY19yZl93ZiA8LSB3b3JrZmxvdygpICU+JQogIGFkZF9yZWNpcGUobGNfcmZfcmVjaXBlKSAlPiUKICBhZGRfbW9kZWwobGNfcmZfbW9kKQpgYGAKCjkuIFNldCB1cCB0aGUgbW9kZWwgdHVuaW5nIGZvciBib3RoIHRoZSBgbXRyeWAgYW5kIGBtaW5fbmAgcGFyYW1ldGVycy4gQmUgc3VyZSB0byBhZGQgdGhlIGBjb250cm9sX3N0YWNrX2dyaWQoKWAgZm9yIHRoZSBgY29udHJvbGAgYXJndW1lbnQgc28gd2UgY2FuIHVzZSB0aGVzZSByZXN1bHRzIGxhdGVyIHdoZW4gd2Ugc3RhY2suIFVzZSBvbmx5IDMgbGV2ZWxzIGluIHRoZSBncmlkLiBGb3IgdGhlIGBtdHJ5YCBwYXJhbWV0ZXIsIHlvdSBuZWVkIHRvIHB1dCBgZmluYWxpemUobXRyeSgpLCBsZW5kaW5nX3RyYWluaW5nICU+JSBzZWxlY3QoLUNsYXNzKSlgIGluIGFzIGFuIGFyZ3VtZW50IGluc3RlYWQgb2YganVzdCBgbXRyeSgpYCwgd2hlcmUgYGxlbmRpbmdfdHJhaW5pbmdgIGlzIHRoZSBuYW1lIG9mIHlvdXIgdHJhaW5pbmcgZGF0YS4gVGhpcyBpcyBiZWNhdXNlIHRoZSBgbXRyeSgpYCBncmlkIHdpbGwgb3RoZXJ3aXNlIGhhdmUgdW5rbm93bnMgaW4gaXQuIFRoaXMgcGFydCBjYW4gdGFrZSBhIHdoaWxlIHRvIHJ1bi4KCmBgYHtyIHJmLXR1bmV9CnJmX2dyaWQgPC0gZ3JpZF9yZWd1bGFyKGZpbmFsaXplKG10cnkoKSwgbGNfdHJhaW5pbmcgJT4lIHNlbGVjdCgtQ2xhc3MpKSwKICAgICAgICAgICAgICAgICAgICAgICAgbWluX24oKSwKICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gMykKCmxjX3JmX3R1bmUgPC0gbGNfcmZfd2YgJT4lCiAgdHVuZV9ncmlkKHJlc2FtcGxlcyA9IGxjX2N2LAogICAgICAgICAgICBjb250cm9sID0gY3RybF9ncmlkLAogICAgICAgICAgICBncmlkID0gcmZfZ3JpZCwKICAgICAgICAgICAgbWV0cmljcyA9IG1ldHJpYykKYGBgCgoxMC4gRmluZCB0aGUgYmVzdCB0dW5pbmcgcGFyYW1ldGVycy4gV2hhdCBpcyB0aGUgYXJlIHRoZSBhY2N1cmFjeSBhbmQgYXJlYSB1bmRlciB0aGUgUk9DIGN1cnZlIGZvciB0aGUgbW9kZWwgd2l0aCB0aG9zZSB0dW5pbmcgcGFyYW1ldGVycz8KCmBgYHtyIGxhc3NvLWJlc3QtdHVuZX0KYmVzdF9sYXNzb19wYXJhbSA8LSBsY19sYXNzb190dW5lICU+JQogIHNlbGVjdF9iZXN0KCkKCmxjX2xhc3NvX3R1bmUgJT4lCiAgY29sbGVjdF9tZXRyaWNzKCkgJT4lCiAgZmlsdGVyKHBlbmFsdHkgPT0gYmVzdF9sYXNzb19wYXJhbSRwZW5hbHR5KQpgYGAKCmBgYHtyIHJmLWJlc3QtdHVuZX0KYmVzdF9yZl9wYXJhbSA8LSBsY19yZl90dW5lICU+JQogIHNlbGVjdF9iZXN0KCkKCmxjX3JmX3R1bmUgJT4lCiAgY29sbGVjdF9tZXRyaWNzKCkgJT4lCiAgZmlsdGVyKG10cnkgPT0gYmVzdF9yZl9wYXJhbSRtdHJ5ICYgbWluX24gPT0gYmVzdF9yZl9wYXJhbSRtaW5fbikKYGBgCgoxMS4gVXNlIGZ1bmN0aW9ucyBmcm9tIHRoZSBgREFMRVhgIGFuZCBgREFMRVh0cmFgIGxpYnJhcmllcyB0byBjcmVhdGUgYSBoaXN0b2dyYW0gYW5kIGJveHBsb3Qgb2YgdGhlIHJlc2lkdWFscyBmcm9tIHRoZSB0cmFpbmluZyBkYXRhLiBIb3cgZG8gdGhleSBsb29rPyBBbnkgaW50ZXJlc3RpbmcgYmVoYXZpb3I/CgpgYGB7ciBmaW5hbC1tb2RzfQojIGZpdCBmaW5hbCBtb2RlbHMKbGNfbGFzc29fZmluYWxfbW9kIDwtIGxjX2xhc3NvX3dmICU+JQogIGZpbmFsaXplX3dvcmtmbG93KGJlc3RfbGFzc29fcGFyYW0pICU+JQogIGZpdChkYXRhID0gbGNfdHJhaW5pbmcpCgpsY19yZl9maW5hbF9tb2QgPC0gbGNfcmZfd2YgJT4lCiAgZmluYWxpemVfd29ya2Zsb3coYmVzdF9yZl9wYXJhbSkgJT4lCiAgZml0KGRhdGEgPSBsY190cmFpbmluZykKYGBgCgpgYGB7ciBleHBsYWluZXJzLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD01fQojIGNyZWF0ZSBleHBsYWluZXJzIApsYXNzb19leHBsYWluIDwtIAogIGV4cGxhaW5fdGlkeW1vZGVscygKICAgIG1vZGVsID0gbGNfbGFzc29fZmluYWxfbW9kLAogICAgZGF0YSA9IGxjX3RyYWluaW5nICU+JSBzZWxlY3QoLUNsYXNzKSwgCiAgICB5ID0gbGNfdHJhaW5pbmcgJT4lIG11dGF0ZShDbGFzc19udW0gPSBhcy5pbnRlZ2VyKENsYXNzID09Imdvb2QiKSkgJT4lIHB1bGwoQ2xhc3NfbnVtKSwKICAgIGxhYmVsID0gImxhc3NvIiwKICAgIHR5cGUgPSAiY2xhc3NpZmljYXRpb24iCiAgKQoKcmZfZXhwbGFpbiA8LSAKICBleHBsYWluX3RpZHltb2RlbHMoCiAgICBtb2RlbCA9IGxjX3JmX2ZpbmFsX21vZCwKICAgIGRhdGEgPSBsY190cmFpbmluZyAlPiUgc2VsZWN0KC1DbGFzcyksIAogICAgeSA9IGxjX3RyYWluaW5nICU+JSBtdXRhdGUoQ2xhc3NfbnVtID0gYXMuaW50ZWdlcihDbGFzcyA9PSJnb29kIikpICU+JSBwdWxsKENsYXNzX251bSksCiAgICBsYWJlbCA9ICJyZiIsCiAgICB0eXBlID0gImNsYXNzaWZpY2F0aW9uIgogICkKCiMgZ2V0IG1vZGVsIGV2YWx1YXRpb24gbWV0cmljcwpsYXNzb19wZXJmIDwtIG1vZGVsX3BlcmZvcm1hbmNlKGxhc3NvX2V4cGxhaW4sIHR5cGUgPSAiY2xhc3NpZmljYXRpb24iKQpyZl9wZXJmIDwtIG1vZGVsX3BlcmZvcm1hbmNlKHJmX2V4cGxhaW4pCgojIG1ha2UgdGhlIHBsb3RzCmhpc3RfcGxvdCA8LSBwbG90KHJmX3BlcmYsIGxhc3NvX3BlcmYsIGdlb20gPSAiaGlzdG9ncmFtIikKYm94X3Bsb3QgPC0gcGxvdChsYXNzb19wZXJmLCByZl9wZXJmLCBnZW9tID0gImJveHBsb3QiKQoKaGlzdF9wbG90ICsgYm94X3Bsb3QKYGBgCgoqKkJldHdlZW4gdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwgYW5kIExBU1NPLCB0aGUgcmVzaWR1YWxzIGZyb20gdGhlIGZvcm1lciBsb29rIG11Y2ggYmV0dGVyLiBUaGV5IGNlbnRlciBhcm91bmQgMCwgaGF2ZSBhIHNtYWxsZXIgc3ByZWFkLCBhbmQgdGhlIFJNU0UgaXMgdmVyeSBjbG9zZSB0byAwLioqCgoxMi4gVXNlIGBEQUxFWGAgZnVuY3Rpb25zIHRvIGNyZWF0ZSBhIHZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdCBmcm9tIHRoaXMgbW9kZWwuIFdoYXQgYXJlIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXM/IAoKYGBge3IgdmFyLWltcCwgZmlnLndpZHRoPTEwLCBmaWQuaGVpZ2h0PTZ9CnNldC5zZWVkKDQ5NCkgCgpsYXNzb192YXJfaW1wIDwtIG1vZGVsX3BhcnRzKGxhc3NvX2V4cGxhaW4pCnJmX3Zhcl9pbXAgPC0gbW9kZWxfcGFydHMocmZfZXhwbGFpbikKcGxvdChsYXNzb192YXJfaW1wLCBzaG93X2JveHBsb3RzID0gVFJVRSkgKyBwbG90KHJmX3Zhcl9pbXAsIHNob3dfYm94cGxvdHMgPSBUUlVFKQpgYGAKCioqVGhlIGZvdXIgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzIGZyb20gTEFTU08gYXJlIGBpbnRfcmF0ZWAsIGB0b3RhbF9iYWxfaWxgLCBgdG90YWxfaWxfaGlnaF9jcmVkaXRfbGltaXRgLioqCgoqKkZvciByYW5kb20gZm9yZXN0LCB0aGV5IGFyZSBgaW50X3JhdGVgLCBgc3ViX2dyYWRlYCwgYGFubnVhbF9pbmNgLCBgb3Blbl9pbF8xMm1gLioqCgoxMy4gV3JpdGUgYSBmdW5jdGlvbiBjYWxsZWQgYGNwX3Byb2ZpbGVgIHRvIG1ha2UgYSBDUCBwcm9maWxlLiBUaGUgZnVuY3Rpb24gd2lsbCB0YWtlIGFuIGV4cGxhaW5lciwgYSBuZXcgb2JzZXJ2YXRpb24sIGFuZCBhIHZhcmlhYmxlIG5hbWUgYXMgaXRzIGFyZ3VtZW50cyBhbmQgY3JlYXRlIGEgQ1AgcHJvZmlsZSBmb3IgYSBxdWFudGl0YXRpdmUgcHJlZGljdG9yIHZhcmlhYmxlLiBZb3Ugd2lsbCBuZWVkIHRvIHVzZSB0aGUgYHByZWRpY3RfcHJvZmlsZSgpYCBmdW5jdGlvbiBpbnNpZGUgdGhlIGZ1bmN0aW9uIHlvdSBjcmVhdGUgLSBwdXQgdGhlIHZhcmlhYmxlIG5hbWUgdGhlcmUgc28gdGhlIHBsb3R0aW5nIHBhcnQgaXMgZWFzaWVyLiBZb3UnbGwgYWxzbyB3YW50IHRvIHVzZSBgYWVzX3N0cmluZygpYCByYXRoZXIgdGhhbiBgYWVzKClgIGFuZCBxdW90ZSB0aGUgdmFyaWFibGVzLiBVc2UgdGhlIGBjcF9wcm9maWxlKClgIGZ1bmN0aW9uIHRvIGNyZWF0ZSBvbmUgQ1AgcHJvZmlsZSBvZiB5b3VyIGNob29zaW5nLiBCZSBzdXJlIHRvIGNob29zZSBhIHZhcmlhYmxlIHRoYXQgaXMgbnVtZXJpYywgbm90IGludGVnZXIuIFRoZXJlIHNlZW0gdG8gYmUgaXNzdWVzIHdpdGggdGhvc2UgdGhhdCBJJ20gbG9va2luZyBpbnRvLgoKYGBge3IgY3BwfQpjcF9wcm9maWxlIDwtIGZ1bmN0aW9uKGV4cGxhaW5lciwgbmV3X29icywgdmFyX25hbWUpIHsKICBjcHAgPC0gcHJlZGljdF9wcm9maWxlKGV4cGxhaW5lciA9IGV4cGxhaW5lciwgbmV3X29ic2VydmF0aW9uID0gbmV3X29icywgdmFyaWFibGVzID0gdmFyX25hbWUpIAogIAogIGNwcCAlPiUKICAgIHJlbmFtZSh5aGF0ID0gYF95aGF0X2ApICU+JQogICAgZmlsdGVyKGBfdm5hbWVfYCAlaW4lIGModmFyX25hbWUpKSAlPiUKICAgIGdncGxvdChhZXNfc3RyaW5nKHggPSB2YXJfbmFtZSwgeSA9ICJ5aGF0IikpICsKICAgIGdlb21fbGluZSgpCn0KYGBgCgpgYGB7ciBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD00fQpzZXQuc2VlZCg0OTQpCmNwX3Byb2ZpbGUocmZfZXhwbGFpbiwgbGNfdHJhaW5pbmcgJT4lIHNhbXBsZV9uKHNpemUgPSAxKSwgImludF9yYXRlIikKYGBgCgpGb3IgYW4gZXh0cmEgY2hhbGxlbmdlLCB3cml0ZSBhIGZ1bmN0aW9uIHRoYXQgd2lsbCB3b3JrIGZvciBlaXRoZXIgYSBxdWFudGl0YXRpdmUgb3IgY2F0ZWdvcmljYWwgdmFyaWFibGUuIAoKSWYgeW91IG5lZWQgaGVscCB3aXRoIGZ1bmN0aW9uIHdyaXRpbmcgY2hlY2sgb3V0IHRoZSBbRnVuY3Rpb25zXShodHRwczovL3I0ZHMuaGFkLmNvLm56L2Z1bmN0aW9ucy5odG1sKSBjaGFwdGVyIG9mIFI0RFMgYnkgV2lja2hhbSBhbmQgR3JvbGVtdW5kLgoKMTQuIFVzZSBgREFMRVhgIGZ1bmN0aW9ucyB0byBjcmVhdGUgcGFydGlhbCBkZXBlbmRlbmNlIHBsb3RzICh3aXRoIHRoZSBDUCBwcm9maWxlcyBpbiBncmF5KSBmb3IgdGhlIDMtNCBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMuIElmIHRoZSBpbXBvcnRhbnQgdmFyaWFibGVzIGFyZSBjYXRlZ29yaWNhbCwgeW91IGNhbiBpbnN0ZWFkIG1ha2UgYSBDUCBwcm9maWxlIGZvciAzIG9ic2VydmF0aW9ucyBpbiB0aGUgZGF0YXNldCBhbmQgZGlzY3VzcyBob3cgeW91IGNvdWxkIGdvIGFib3V0IGNvbnN0cnVjdGluZyBhIHBhcnRpYWwgZGVwZW5kZW5jZSBwbG90IGZvciBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlICh5b3UgZG9uJ3QgaGF2ZSB0byBjb2RlIGl0LCBidXQgeW91IGNhbiBpZiB5b3Ugd2FudCBhbiBleHRyYSBjaGFsbGVuZ2UpLiBJZiBpdCBldmVyIGdpdmVzIHlvdSBhbiBlcnJvciB0aGF0IHNheXMsICJFcnJvcjogQ2FuJ3QgY29udmVydCBmcm9tIGBWQVJJQUJMRWAgPGRvdWJsZT4gdG8gYFZBUklBQkxFYCA8aW50ZWdlcj4gZHVlIHRvIGxvc3Mgb2YgcHJlY2lzaW9uIiwgdGhlbiByZW1vdmUgdGhhdCB2YXJpYWJsZSBmcm9tIHRoZSBsaXN0LiBJIHNlZW0gdG8gaGF2ZSBmaWd1cmVkIG91dCB3aHkgaXQncyBkb2luZyB0aGF0LCBidXQgSSBkb24ndCBrbm93IGhvdyB0byBmaXggaXQgeWV0LgoKYGBge3IgcGRwLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD01fQpzZXQuc2VlZCg0OTQpIApyZl9wZHAgPC0gbW9kZWxfcHJvZmlsZSgKICBleHBsYWluZXIgPSByZl9leHBsYWluLAogIHZhcmlhYmxlcyA9IGMoImludF9yYXRlIiwgImFubnVhbF9pbmMiLCAib3Blbl9pbF8xMm0iKSkKCnBsb3QocmZfcGRwLCAKICAgICB2YXJpYWJsZXMgPSBjKCJpbnRfcmF0ZSIsICJhbm51YWxfaW5jIiwgIm9wZW5faWxfMTJtIiksCiAgICAgZ2VvbSA9ICJwcm9maWxlcyIpCmBgYAoKMTUuIEZpdCBvbmUgbW9yZSBtb2RlbCB0eXBlIG9mIHlvdXIgY2hvb3NpbmcgdGhhdCB3aWxsIGZlZWQgaW50byB0aGUgc3RhY2tpbmcgbW9kZWwuIAoKYGBge3Iga25ufQojIGNyZWF0ZSBhIG1vZGVsIGRlZmluaXRpb24KbGNfa25uX21vZCA8LQogIG5lYXJlc3RfbmVpZ2hib3IoCiAgICBuZWlnaGJvcnMgPSB0dW5lKCJrIikKICApICU+JQogIHNldF9lbmdpbmUoImtrbm4iKSAlPiUgCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikKCiMgY3JlYXRlIHRoZSB3b3JrZmxvdwpsY19rbm5fd2YgPC0gCiAgd29ya2Zsb3coKSAlPiUgCiAgYWRkX21vZGVsKGxjX2tubl9tb2QpICU+JQogIGFkZF9yZWNpcGUobGNfbGFzc29fcmVjaXBlKQoKIyB0dW5lIGl0IHVzaW5nIDQgdHVuaW5nIHBhcmFtZXRlcnMKbGNfa25uX3R1bmUgPC0gCiAgbGNfa25uX3dmICU+JSAKICB0dW5lX2dyaWQoCiAgICBsY19jdiwKICAgIG1ldHJpY3MgPSBtZXRyaWMsCiAgICBncmlkID0gNCwKICAgIGNvbnRyb2wgPSBjdHJsX2dyaWQKICApCmBgYAoKCjE2LiBDcmVhdGUgYSBtb2RlbCBzdGFjayB3aXRoIHRoZSBjYW5kaWRhdGUgbW9kZWxzIGZyb20gdGhlIHByZXZpb3VzIHBhcnRzIG9mIHRoZSBleGVyY2lzZSBhbmQgdXNlIHRoZSBgYmxlbmRfcHJlZGljdGlvbnMoKWAgZnVuY3Rpb24gdG8gZmluZCB0aGUgY29lZmZpY2llbnRzIG9mIHRoZSBzdGFja2VkIG1vZGVsLiBDcmVhdGUgYSBwbG90IGV4YW1pbmluZyB0aGUgcGVyZm9ybWFuY2UgbWV0cmljcyBmb3IgdGhlIGRpZmZlcmVudCBwZW5hbHR5IHBhcmFtZXRlcnMgdG8gYXNzdXJlIHlvdSBoYXZlIGNhcHR1cmVkIHRoZSBiZXN0IG9uZS4gSWYgbm90LCBhZGp1c3QgdGhlIHBlbmFsdHkuIChISU5UOiB1c2UgdGhlIGBhdXRvcGxvdCgpYCBmdW5jdGlvbikuIFdoaWNoIG1vZGVscyBhcmUgY29udHJpYnV0aW5nIG1vc3Q/CgpgYGB7ciBzdGFja30KbGNfc3RhY2sgPC0gCiAgc3RhY2tzKCkgJT4lIAogIGFkZF9jYW5kaWRhdGVzKGxjX3JmX3R1bmUpICU+JSAKICBhZGRfY2FuZGlkYXRlcyhsY19sYXNzb190dW5lKSAlPiUgCiAgYWRkX2NhbmRpZGF0ZXMobGNfa25uX3R1bmUpCmBgYAoKYGBge3IgYmxlbmQsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTR9CihsY19ibGVuZCA8LSAKICAgbGNfc3RhY2sgJT4lCiAgIGJsZW5kX3ByZWRpY3Rpb25zKCkpCgphdXRvcGxvdChsY19ibGVuZCkKYGBgCgoqKlJhbmRvbSBmb3Jlc3QgaXMgdGhlIG9ubHkgbW9kZWwgdGhhdCB3YXMgcmV0YWluZWQuIEkgZ290IHZhcnlpbmcgcmVzdWx0cyBieSBjaGFuZ2luZyB0aGUgcGFyYW1ldGVycyB0byB0aGUgcHJldmlvdXMgdHVuaW5nIHN0ZXBzIGJ1dCBvdmVyYWxsIGl0IHNlZW1zIGxpa2UgcmFuZG9tIGZvcmVzdCBhbHdheXMgY2FtZSBvdXQgb24gdG9wIHdpdGggdGhlIGdyZWF0ZXN0IHdlaWdodC4gVGhlIGNvZWZmaWNpZW50cyBvbiB0aGUgb3RoZXIgbW9kZWxzIHdoZW4gdGhleSB3ZXJlIGluY2x1ZGVkIHdlcmUgdmVyeSBzbWFsbCAoYXJvdW5kIG9yIGxlc3MgdGhhbiAxKS4gVGhpcyBhbHNvIG1hZGUgdGhlIGZpbmFsIG1vZGVsIHNtYWxsZXIgaW4gdGVybXMgb2YgZmlsZSBzaXplIGJlY2F1c2UgaXQgY291bGQgZXhjZWVkIDEwMCBNQiBhbmQgaWYgc28gdGhlbiBJIHdvdWxkbid0IGJlIGFibGUgdG8gdXBsb2FkIHRvIEdpdGh1Yi4qKgoKMTcuIEZpdCB0aGUgZmluYWwgc3RhY2tlZCBtb2RlbCB1c2luZyBgZml0X21lbWJlcnMoKWAuIEFwcGx5IHRoZSBtb2RlbCB0byB0aGUgdGVzdCBkYXRhIGFuZCByZXBvcnQgdGhlIGFjY3VyYWN5IGFuZCBhcmVhIHVuZGVyIHRoZSBjdXJ2ZS4gQ3JlYXRlIGEgZ3JhcGggb2YgdGhlIFJPQyBhbmQgY29uc3RydWN0IGEgY29uZnVzaW9uIG1hdHJpeC4gQ29tbWVudCBvbiB3aGF0IHlvdSBzZWUuIFNhdmUgdGhpcyBmaW5hbCBtb2RlbCB1c2luZyB0aGUgYHNhdmVSRFMoKWAgZnVuY3Rpb24gLSBzZWUgdGhlIFtVc2UgdGhlIG1vZGVsXShodHRwczovL2FkdmFuY2VkLWRzLWluLXIubmV0bGlmeS5hcHAvcG9zdHMvMjAyMS0wMy0xNi1tbC1yZXZpZXcvI3VzZS10aGUtbW9kZWwpIHNlY3Rpb24gb2YgdGhlIGB0aWR5bW9kZWxzYCBpbnRyby4gV2UgYXJlIGdvaW5nIHRvIHVzZSB0aGUgbW9kZWwgaW4gdGhlIG5leHQgcGFydC4gWW91J2xsIHdhbnQgdG8gc2F2ZSBpdCBpbiB0aGUgZm9sZGVyIHdoZXJlIHlvdSBjcmVhdGUgeW91ciBzaGlueSBhcHAuCgpgYGB7ciBmaW5hbC1zdGFjaywgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NH0KKGxjX2ZpbmFsX3N0YWNrIDwtIGxjX2JsZW5kICU+JQogICBmaXRfbWVtYmVycykKCiMgbWFrZSBwcmVkaWN0aW9ucyBvbiB0ZXN0IGRhdGEKKGxjX3N0YWNrX3ByZWRzIDwtIAogICAgY2JpbmQobGNfZmluYWxfc3RhY2sgJT4lIHByZWRpY3QobmV3X2RhdGEgPSBsY190ZXN0aW5nKSwKICAgICAgICAgIGxjX2ZpbmFsX3N0YWNrICU+JSBwcmVkaWN0KG5ld19kYXRhID0gbGNfdGVzdGluZywgdHlwZSA9ICJwcm9iIikpICU+JQogICAgYmluZF9jb2xzKGxjX3Rlc3RpbmcpICU+JQogICAgcmVsb2NhdGUoQ2xhc3MsIC5hZnRlciA9IC5wcmVkX2dvb2QpKQogIAojIFJPQyBBVUMKbGNfc3RhY2tfcHJlZHMgJT4lCiAgIyB0aGUgQ2xhc3MgbGV2ZWwgaXMgKGJhZCwgZ29vZCkgc28gd2UgbmVlZCB0byBzcGVjaWZ5IHRoYXQgdGhlIGV2ZW50IGlzIHRoZSBzZWNvbmQgb25lCiAgcm9jX2F1YygucHJlZF9nb29kLCB0cnV0aCA9IENsYXNzLCBldmVudF9sZXZlbCA9ICJzZWNvbmQiKQoKIyBhY2N1cmFjeQpsY19zdGFja19wcmVkcyAlPiUKICAjIHRoZSBDbGFzcyBsZXZlbCBpcyAoYmFkLCBnb29kKSBzbyB3ZSBuZWVkIHRvIHNwZWNpZnkgdGhhdCB0aGUgZXZlbnQgaXMgdGhlIHNlY29uZCBvbmUKICBhY2N1cmFjeSgucHJlZF9jbGFzcywgdHJ1dGggPSBDbGFzcywgZXZlbnRfbGV2ZWwgPSAic2Vjb25kIikKCiMgZ3JhcGggb2YgUk9DCmxjX3N0YWNrX3ByZWRzICU+JQogICMgdGhlIENsYXNzIGxldmVsIGlzIChiYWQsIGdvb2QpIHNvIHdlIG5lZWQgdG8gc3BlY2lmeSB0aGF0IHRoZSBldmVudCBpcyB0aGUgc2Vjb25kIG9uZQogIHJvY19jdXJ2ZSgucHJlZF9nb29kLCB0cnV0aCA9IENsYXNzLCBldmVudF9sZXZlbCA9ICJzZWNvbmQiKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSAxIC0gc3BlY2lmaWNpdHksIHkgPSBzZW5zaXRpdml0eSkpICsKICBnZW9tX3BhdGgoKSArCiAgZ2VvbV9hYmxpbmUobHR5ID0gMykgKwogIGNvb3JkX2VxdWFsKCkgKwogIGxhYnMoeSA9ICJUcnVlIFBvc2l0aXZlIFJhdGUgKFNlbnNpdGl2aXR5KSIsCiAgICAgICB4ID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUiLAogICAgICAgZmlsbCA9IE5VTEwsCiAgICAgICB0aXRsZSA9ICJST0MgQ3VydmUiLAogICAgICAgc3VidGl0bGUgPSAiU3RhY2sgTW9kZWwiKQoKIyBjb25mdXNpb24gbWF0cml4CmxjX3N0YWNrX3ByZWRzICU+JQogIGNvbmZfbWF0KENsYXNzLCAucHJlZF9jbGFzcykgJT4lCiAgcGx1Y2soMSkgJT4lCiAgYXNfdGliYmxlKCkgJT4lCiAgZ2dwbG90KGFlcyhQcmVkaWN0aW9uLCBUcnV0aCwgYWxwaGEgPSBuKSkgKwogIGdlb21fdGlsZShzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IG4pLCBjb2xvdXIgPSAid2hpdGUiLCBhbHBoYSA9IDEsIHNpemUgPSA4KSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIGxhYnMoeSA9ICJBY3R1YWwgQ2xhc3MiLAogICAgICAgeCA9ICJQcmVkaWN0ZWQgQ2xhc3MiLAogICAgICAgZmlsbCA9IE5VTEwsCiAgICAgICB0aXRsZSA9ICJDb25mdXNpb24gTWF0cml4IiwKICAgICAgIHN1YnRpdGxlID0gIlN0YWNrIE1vZGVsIikKYGBgCgoqKlRoZSBpZGVhbCBST0MgY3VydmUgaXMgb25lIHRoYXQgZ29lcyBoaWdoIHVwIHRoZSB1cHBlci1sZWZ0IGNvcm5lciBhbmQgdGhhdCBpcyB0aGUgYmVoYXZpb3Igd2Ugc2VlIGluIHRoZSBwbG90LiBPbiB0b3Agb2YgdGhpcywgYWxtb3N0IGFsbCBvZiB0aGUgb2JzZXJ2YXRpb25zIGluIHRoZSB0ZXN0IGRhdGEgd2VyZSBjbGFzc2lmaWVkIGNvcnJlY3RseS4qKgoKYGBge3IgZXZhbD1GQUxTRX0Kc2F2ZVJEUyhsY19maW5hbF9zdGFjaywgImxlbmRpbmdfY2x1Yl9zdGFja19maXQucmRzIikKYGBgCgojIyBTaGlueSBhcHAKCklmIHlvdSBhcmUgbmV3IHRvIFNoaW55IGFwcHMgb3IgaXQncyBiZWVuIGF3aGlsZSBzaW5jZSB5b3UndmUgbWFkZSBvbmUsIHZpc2l0IHRoZSBTaGlueSBsaW5rcyBvbiBvdXIgY291cnNlIFtSZXNvdXJjZV0oaHR0cHM6Ly9hZHZhbmNlZC1kcy1pbi1yLm5ldGxpZnkuYXBwL3Jlc291cmNlcy5odG1sKSBwYWdlLiBJIHdvdWxkIHJlY29tbWVuZCBzdGFydGluZyB3aXRoIG15IHJlc291cmNlIGJlY2F1c2UgaXQgd2lsbCBiZSB0aGUgbW9zdCBiYXNpYy4gWW91IHdvbid0IGJlIGRvaW5nIGFueXRoaW5nIHN1cGVyIGZhbmN5IGluIHRoaXMgYXBwLiAKCkV2ZXJ5b25lIHNob3VsZCB3YXRjaCB0aGUgW1RoZW1pbmcgU2hpbnldKGh0dHBzOi8veW91dHUuYmUvYjlXV05PNFAyblkpIHRhbGsgYnkgQ2Fyc29uIFNpZXZlcnQgc28geW91IGNhbiBtYWtlIHlvdXIgYXBwIGxvb2sgYW1hemluZy4KCioqVGFza3M6KioKCllvdSBhcmUgZ29pbmcgdG8gY3JlYXRlIGFuIGFwcCB0aGF0IGFsbG93cyBhIHVzZXIgdG8gZXhwbG9yZSBob3cgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0eSBvZiBhIGxvYW4gYmVpbmcgcGFpZCBiYWNrIChvciBtYXliZSBqdXN0IHRoZSBwcmVkaWN0ZWQgY2xhc3MgLSBlaXRoZXIgImdvb2QiIG9yICJiYWQiKSBjaGFuZ2VzIGRlcGVuZGluZyBvbiB0aGUgdmFsdWVzIG9mIHRoZSBwcmVkaWN0b3IgdmFyaWFibGVzLgoKU3BlY2lmaWNhbGx5LCB5b3Ugd2lsbCBkbyB0aGUgZm9sbG93aW5nOgoKKiBTZXQgdXAgYSBzZXBhcmF0ZSBwcm9qZWN0IGFuZCBHaXRIdWIgcmVwbyBmb3IgdGhpcyBhcHAuIE1ha2Ugc3VyZSB0aGUgc2F2ZWQgbW9kZWwgZnJvbSB0aGUgcHJldmlvdXMgcHJvYmxlbSBpcyBhbHNvIGluIHRoYXQgZm9sZGVyLiBUaGUgYXBwIG5lZWRzIHRvIGJlIGNyZWF0ZWQgaW4gYSBmaWxlIGNhbGxlZCAqZXhhY3RseSogYXBwLlIgdGhhdCBpcyBhbHNvIGluIHRoZSBwcm9qZWN0IGZvbGRlci4gICAKKiBBdCB0aGUgdG9wIG9mIHRoZSBmaWxlLCBsb2FkIGFueSBsaWJyYXJpZXMgeW91IHVzZSBpbiB0aGUgYXBwLiAgCiogVXNlIHRoZSBgcmVhZFJEUygpYCBmdW5jdGlvbiB0byBsb2FkIHRoZSBtb2RlbC4gIAoqIFlvdSBtYXkgd2FudCB0byBsb2FkIHNvbWUgb2YgdGhlIGRhdGEgdG8gdXNlCiogQ3JlYXRlIGEgdXNlciBpbnRlcmZhY2UgKHVzaW5nIHRoZSB2YXJpb3VzIGAqSW5wdXQoKWAgZnVuY3Rpb25zKSB3aGVyZSBzb21lb25lIGNvdWxkIGVudGVyIHZhbHVlcyBmb3IgZWFjaCB2YXJpYWJsZSB0aGF0IGZlZWRzIGludG8gdGhlIG1vZGVsLiBZb3Ugd2lsbCB3YW50IHRvIHRoaW5rIGhhcmQgYWJvdXQgd2hpY2ggdHlwZXMgb2YgYCpJbnB1dCgpYCBmdW5jdGlvbnMgdG8gdXNlLiBUaGluayBhYm91dCBob3cgeW91IGNhbiBiZXN0IHByZXZlbnQgbWlzdGFrZXMgKGVnLiBlbnRlcmluZyBmcmVlIHRleHQgY291bGQgbGVhZCB0byBtYW55IG1pc3Rha2VzKS4gCiogQW5vdGhlciBwYXJ0IG9mIHRoZSB1c2VyIGludGVyZmFjZSB3aWxsIGFsbG93IHRoZW0gdG8gY2hvb3NlIGEgdmFyaWFibGUgKHlvdSBjYW4gbGltaXQgdGhpcyB0byBvbmx5IHRoZSBxdWFudGl0YXRpdmUgdmFyaWFibGVzKSB3aGVyZSB0aGV5IGNhbiBleHBsb3JlIHRoZSBlZmZlY3RzIG9mIGNoYW5naW5nIHRoYXQgdmFyaWFibGUsIGhvbGRpbmcgYWxsIG90aGVycyBjb25zdGFudC4gIAoqIEFmdGVyIHRoZSB1c2VyIGhhcyBlbnRlcmVkIGFsbCB0aGUgcmVxdWlyZWQgdmFsdWVzLCB0aGUgb3V0cHV0IHdpbGwgYmUgYSBDUCBwcm9maWxlIHdpdGggdGhlIHRoZSBwcmVkaWN0ZWQgdmFsdWUgZm9yIHRoZSBkYXRhIHRoYXQgd2FzIGVudGVyZWQsIGluZGljYXRlZCBieSBhIHBvaW50LiBJIGRvbid0IHRoaW5rIHRoZSBmdW5jdGlvbnMgZnJvbSBgREFMRVhgIGFuZCBgREFMRVh0cmFgIHdpbGwgd29yayB3aXRoIGEgc3RhY2tlZCBtb2RlbCwgc28geW91J2xsIGxpa2VseSBoYXZlIHRvIChnZXQgdG8pIGRvIHNvbWUgb2YgeW91ciBvd24gY29kaW5nLiAKKiBVc2UgdGhlIGBic2xpYmAgdG8gdGhlbWUgeW91ciBzaGlueSBhcHAhICAKKiBQdWJsaXNoIHlvdXIgYXBwIHRvIFtzaGlueWFwcHMuaW9dKGh0dHBzOi8vd3d3LnNoaW55YXBwcy5pby8pLiBUaGVyZSBhcmUgaW5zdHJ1Y3Rpb25zIGZvciBkb2luZyB0aGF0IG9uIHRoZSB0dXRvcmlhbCBJIGxpbmtlZCB0byBhYm92ZS4gICAKKiBXcml0ZSBhIHBhcmFncmFwaCBvciB0d28gZGVzY3JpYmluZyB5b3VyIGFwcCBvbiB5b3VyIHdlYnNpdGUhIExpbmsgdG8gdGhlIGFwcCBhbmQgeW91ciBHaXRIdWIgcmVwb3NpdG9yeSBpbiB5b3VyIHBvc3QuIEluY2x1ZGUgYSBsaW5rIHRvIHlvdXIgcG9zdCBoZXJlLiAKCgojIyBDb2RlZCBCaWFzCgpXYXRjaCB0aGUgW0NvZGUgQmlhc10oaHR0cHM6Ly93d3cucGJzLm9yZy9pbmRlcGVuZGVudGxlbnMvZmlsbXMvY29kZWQtYmlhcy8pIGZpbG0gYW5kIHdyaXRlIGEgc2hvcnQgcmVmbGVjdGlvbi4gSWYgeW91IHdhbnQgc29tZSBwcm9tcHRzLCByZWZsZWN0IG9uOiBXaGF0IHBhcnQgb2YgdGhlIGZpbG0gaW1wYWN0ZWQgeW91IHRoZSBtb3N0PyBXYXMgdGhlcmUgYSBwYXJ0IHRoYXQgc3VycHJpc2VkIHlvdSBhbmQgd2h5PyBXaGF0IGVtb3Rpb25zIGRpZCB5b3UgZXhwZXJpZW5jZSB3aGlsZSB3YXRjaGluZz8KCioqSSB0aG91Z2h0IHRoYXQgQ29kZWQgQmlhcyB3YXMgZXhjZWxsZW50LiBJdCBjb3ZlcnMgYWxsIG9mIHRoZSBtb3N0IHByZXNzaW5nIGlzc3VlcyBwb3NlZCBieSBBSSB0ZWNobm9sb2d5IHRoYXQgd2UgY3VycmVudGx5IGZhY2UgaW4gYSB3YXkgdGhhdCB3YXMgdmVyeSBkaWdlc3RpYmxlLiBUaGVyZSB3YXMgbm90aGluZyBvdmVybHkgdGVjaG5pY2FsIGFib3V0IHRoZSBkaXNjdXNzaW9ucyB0aGF0IHdlcmUgYnJvdWdodCB1cCwgd2hpY2ggbWFkZSB0aGUgZmlsbSBhcHByb2FjaGFibGUgdG8gYSB3aWRlIGF1ZGllbmNlIGJleW9uZCB0aG9zZSB3aG8gd29yayBpbiB0ZWNoLiBJIHRoaW5rIHRoaXMgaXMgY3J1Y2lhbCBhcyB0aGVzZSB0eXBlcyBvZiBzb2Z0d2FyZSBhcmUgYmVpbmcgZGVwbG95ZWQgb24gYSBsYXJnZSBzY2FsZSBpbiBhIHdpZGUgdmFyaWV0eSBvZiBmaWVsZHMuIEFzIHRoZXkgaGF2ZSBhY2Nlc3MgdG8gcGVvcGxl4oCZcyBkYXRhIGFuZCBtYWtlIGRlY2lzaW9ucyB0aGF0IGNhbiBoYXZlIHZlcnkgc2VyaW91cyBjb25zZXF1ZW5jZXMsIHBlb3BsZSBmaXJzdCBuZWVkIHRvIGJlIG1hZGUgYXdhcmUgdGhhdCB0aGVpciBwcml2YWN5IGFuZCBkYXRhIGFyZSBiZWluZyBtb25pdG9yZWQgYW5kIHBvdGVudGlhbGx5IGV4cGxvaXRlZC4gSXQgaXMgb25lIHRoaW5nIGZvciBhIHBlcnNvbiB0byBiZSBsZXNzIGNhcmVmdWwgYWJvdXQgdGhlaXIgb25saW5lIHByZXNlbmNlIGJlY2F1c2UgYSBjb21wYW55IG1pZ2h0IGJlIHRyYWNraW5nIHRoZWlyIGFjdGl2aXR5LCBidXQgaXQgYmVjb21lcyB2ZXJ5IHNlcmlvdXMgdGhlIG91dGNvbWVzIG9mIHRoZWlyIGpvYiBhcHBsaWNhdGlvbiwgaGVhbHRoIGluc3VyYW5jZSwgY3JlZGl0IHNjb3JlLCBldGMuIGFyZSBiZWluZyBkZWNpZGVkIGJ5IGFuIGVzc2VudGlhbGx5IGJsYWNrLWJveCBwcm9jZXNzIGFuZCB0aGF0IHRoZXkgaGF2ZSBubyBpZGVhIHRoaXMgaXMgd2hhdCBoYXBwZW5zIGJlaGluZCB0aGUgc2NlbmVzLiBJZiB0aGV5IGRvIG5vdCBrbm93IGFuZCBibGluZGx5IGFjY2VwdCB0aGVzZSBkZWNpc2lvbnMgdGhlbiBob3cgY2FuIHRoZXkgYXBwZWFsIHdoZW4gdGhlIHN5c3RlbSBpcyBpbmFjY3VyYXRlPyBQZXJzb25hbGx5LCB3aGF04oCZcyBtb3JlIHRyb3VibGluZyBpcyB0aGF0IGV2ZW4gdGhlIHBlb3BsZSBiZWhpbmQgdGhlc2UgYWxnb3JpdGhtcyBjYW5ub3QgZnVsbHkgZXhwbGFpbiBob3cgdGhleSBhcnJpdmVkIGF0IHRob3NlIGRlY2lzaW9ucyBvbiBhIGNhc2UtYnktY2FzZSBiYXNpcy4gVGhpcyBpcyBzb21ldGhpbmcgdGhhdCB3YXMgdm9pY2VkIGJ5IG1hbnkgc3BlYWtlcnMgaW4gdGhlIG1vdmllIHNvIGl0IHRydWx5IGlzIGFsYXJtaW5nIGhvdyB0aGVzZSB0b29scyB3ZXJlIGRlcGxveWVkIHdpdGhvdXQgZmlyc3QgYmVpbmcgdGhvcm91Z2hseSBldmFsdWF0ZWQgYW5kIHRlc3RlZCBieSBwYXJ0aWVzIHRoYXQgYXJlIGtub3dsZWRnZWFibGUgYWJvdXQgdGhlIHN1YmplY3QgYXQgaGFuZC4qKgoKKipUaGlzIGlzIHdoZXJlIGd1aWRlbGluZXMgYW5kIHJlZ3VsYXRpb25zIHJlYWxseSBuZWVkIHRvIGNvbWUgaW4uIEZpcnN0IGFuZCBmb3JlbW9zdCB3ZSBuZWVkIHRvIGxpbWl0IHRoZSBhbW91bnQgYW5kIGtpbmQgb2YgZGF0YSBjb21wYW5pZXMgYW5kIHRoZSBzdGF0ZSBjYW4gY29sbGVjdCBhbmQgc2hhcmUgYW5kIHRvIGJlIHRyYW5zcGFyZW50IGFib3V0IGRhdGEgY29sbGVjdGlvbiBhbmQgdXNhZ2UuIEZvciBleGFtcGxlIGluIHRoZSBjYXNlIG9mIGZhY2lhbCByZWNvZ25pdGlvbiB0ZWNobm9sb2d5LCBwZW9wbGUgc2hvdWxkIG5vdCBiZSBzY3JlZW5lZCB3aXRob3V0IGtub3dsZWRnZSBsZXQgYWxvbmUgY29uc2VudCB3aGVuIHRoZXJlIGlzIG5vIGV2aWRlbmNlIG9mIHRoZW0gaGF2aW5nIHZpb2xhdGVkIHNvbWV0aGluZy4gUGVvcGxlIG5lZWQgdG8ga25vdyB3aGF0IHRoZWlyIHBlcnNvbmFsIGRhdGEgaXMgYmVpbmcgdXNlZCBmb3IgYW5kIHdobyBwb3RlbnRpYWxseSBzdGFuZHMgdG8gcHJvZml0IGZyb20gaXQuIEZ1cnRoZXJtb3JlLCB3aGVuIGl0IGNvbWVzIHRvIGFsZ29yaXRobXMsIGFsdGhvdWdoIG1hbnkgb2YgdGhlbSBhcmUgcHJvcHJpZXRhcnksIHRoZSBnb3Zlcm5tZW50IGFuZCBvdGhlciBtb25pdG9yaW5nIGFnZW5jaWVzIGNhbiBzdGlsbCBlbmZvcmNlIHJlZ3VsYXRpb25zIGFuZCBldmFsdWF0aW9uIHByb2Nlc3NlcyB0aGF0IHRoZXkgbXVzdCBjb21wbHkgd2l0aCBiZWZvcmUgYmVpbmcgZGVwbG95ZWQuIEFsb25nIHRoZSBzYW1lIGxpbmVzLCBkZWNpc2lvbnMgbWFkZSBieSBhbGdvcml0aG1zIG5lZWQgdG8gYmUgdHJhbnNwYXJlbnQgYW5kIGNvbWUgd2l0aCBhbiBleHBsYW5hdGlvbi4gUGVvcGxlIGhhdmUgdGhlIHJpZ2h0IHRvIGtub3cgd2h5IGFuZCBldmlkZW50bHkgdGhlIGFnZW5jaWVzIHVzaW5nIHRoZXNlIGFsZ29yaXRobXMgYWxzbyBuZWVkIHRvIGZ1bGx5IHVuZGVyc3RhbmQgdGhlIGRlY2lzaW9ucyB0aGV5IHVsdGltYXRlbHkgYXV0aG9yaXplLiBJZiBsZWZ0IHVuY2hlY2tlZCB0aGVuIHdlIGFyZSBnb2luZyBkb3duIHRoaXMgcGF0aCB3aGVyZSwgYXMgb25lIG9mIHRoZSBzcGVha2VycyBzYWlkLCB3ZSB3b3VsZCBiZSBpbXBsaWNpdGx5IG51ZGdlZCBhbmQgaW5mbHVlbmNlZCBieSBkZWNpc2lvbnMgbWFkZSBieSBjb3Jwb3JhdGlvbnMgYW5kIGdvdmVybm1lbnRzIHRoYXQgYXJlIG5vdCBhbHdheXMgYWN0aW5nIGluIG91ciBiZXN0IGludGVyZXN0cy4gTW9yZW92ZXIsIHdlIG5lZWQgdG8gYWRkcmVzcyBob3cgdGhlc2UgaXNzdWVzIGRpc3Byb3BvcnRpb25hdGVseSBhZmZlY3QgbWFyZ2luYWxpemVkIG9yIHRhcmdldGVkIGdyb3Vwcy4gVGhlc2UgYWxnb3JpdGhtcyBub3Qgb25seSBydW4gb24gZGF0YSBidXQgYWxzbyBwcm9kdWNlIGRhdGEgYXMgdGhleSBhcmUgbWFraW5nIHByZWRpY3Rpb25zLCBjbGFzc2lmaWNhdGlvbnMsIGV0Yy4gc28gdGhleSBjYW4gc2VyaW91c2x5IGFnZ3JhdmF0ZSBpbmp1c3RpY2VzIGFuZCBpbmVxdWl0aWVzLioqCgoqKlVsdGltYXRlbHksIEkgZG8gYmVsaWV2ZSB0aGF0IHRlY2hub2xvZ3kgaXMgcG93ZXJmdWwgYW5kIGNhbiBhZHZhbmNlIGFuZCByZXZvbHV0aW9uaXplIG91ciBsaXZlcyBidXQgd2UgbmVlZCB0byBlbnN1cmUgdGhhdCB0aGUgYmVuZWZpdHMgYW5kIHByb2dyZXNzIGFyZSBlcXVpdGFibHkgZGlzdHJpYnV0ZWQgdG8gYWxsIG1lbWJlcnMgb2Ygc29jaWV0eS4gT2YgY291cnNlIHRoZSBwdWJsaWMgbmVlZCB0byBiZSBhd2FyZSBidXQgdGhleSBjYW4gb25seSBrbm93IHRvIGFuIGV4dGVudC4gUGVvcGxlIHdvcmtpbmcgaW4gQUksIHJlc2VhcmNoZXJzLCBhbmQgcG9saWN5LW1ha2VycyBzaG91bGQgc2hvdWxkZXIgbW9zdCBvZiB0aGUgcmVzcG9uc2liaWxpdHkgZm9yIGNyZWF0aW5nLCBldmFsdWF0aW5nLCBhbmQgbW9uaXRvcmluZyB0aGVzZSB0b29scyB0byBlbnN1cmUgdGhhdCB0aGVpciB1c2Ugd2lsbCBiZSBhcHByb3ByaWF0ZSBhbmQganVzdC4qKgoKClJFTUVNQkVSIFRPIEFERCBZT1VSIEdJVEhVQiBMSU5LIEFUIFRIRSBUT1AgT0YgVEhFIFBBR0UgQU5EIFVOQ09NTUVOVCBUSEUgYGtuaXRyYCBPUFRJT05TLgoKCg==